package com.dmw.demo.utils;

import lombok.extern.slf4j.Slf4j;

import javax.annotation.Resource;
import java.io.*;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 制作索引的类
 */
@Slf4j
public class Parser {

    @Resource
    private Index index;

    // 通过单线程制作索引
    public void run() {
        log.info("索引制作开始");
        long begin = System.currentTimeMillis();
        // 1. 根据上面指定的路径，枚举出该路径中的所有文件（html），包括子目录中的文件
        ArrayList<File> fileList = new ArrayList<>();
        enumFile(SystemConstants.FILE_LOADING_PATH, fileList);
        // 2. 针对上面罗列出的文件的路径，打开文件，读取文件内容，并进行解析构建索引
        for (File file : fileList) {
            // 通过这个方法来解析单个的 HTML 文件
            parseHTML(file);
        }
        // 3. 将内存中构造好的索引数据结构，保存到指定的文件中
        index.save();
        long end = System.currentTimeMillis();
        log.info("索引制作完毕，消耗时间：{}", (end - begin) + " ms");
    }

    // 通过多线程制作索引
    public void runByThread() throws InterruptedException {
        log.info("索引制作开始");
        long begin = System.currentTimeMillis();

        // 1. 枚举出所有的文件
        ArrayList<File> fileList = new ArrayList<>();
        enumFile(SystemConstants.FILE_LOADING_PATH, fileList);

        // 2. 循环遍历文件（通过多线程自作，引入线程池）
        CountDownLatch latch = new CountDownLatch(fileList.size());
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for(File file: fileList) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    parseHTML(file);
                    latch.countDown();
                }
            });
        }
        // await 方法会阻塞，直到所有的任务都调用 countDown 后才结束
        latch.await();
        // 手动把线程池中的线程干掉
        executorService.shutdown();
        // 3. 将内存中构造好的索引数据结构，保存到指定的文件中
        index.save();
        long end = System.currentTimeMillis();
        log.info("索引制作完毕，消耗时间：{}", (end - begin) + " ms");
    }


    /**
     * 解析 HTML 文件
     *
     * @param file
     */
    private void parseHTML(File file) {
        // 1. 解析标题
        String title = parseTitle(file);
        // 2. 解析 URL
        String url = parseURL(file);
        // 3. 解析正文——>简介
        String content = parseContentByRegex(file);
        // 4. 把解析出来的这些信息，加入到索引当中
        index.addDoc(title, url, content);
    }

    private String parseContent(File file) {
        try {
            // 手动把缓冲区设置成 1M
            BufferedReader bufferedReader = new BufferedReader(new FileReader(file), 1024*1024);
            // 加上一个是否是要进行拷贝的开关
            boolean isCopy = true;
            StringBuilder content = new StringBuilder();
            while (true) {
                int ret = bufferedReader.read();
                if (ret == -1) {
                    break;
                }
                char c = (char) ret;
                if (isCopy) {
                    if (c == '<') {
                        isCopy = false;
                        continue;
                    }
                    if (c == '\n' || c == '\r') {
                        // 去掉换行，将换行换成空格
                        c = ' ';
                    }
                    content.append(c);
                } else {
                    if (c == '>') {
                        isCopy = true;
                    }
                }
            }
            bufferedReader.close();
            return content.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 基于正则表达式实现去 标签和 script
     * @param file
     * @return
     */
    public String parseContentByRegex(File file){
        // 1. 先把文件整个内容都读到 String 里面
        String content = readFile(file);
        // 2. 替换掉 script 标签
        content = content.replaceAll(RegexPatterns.SCRIPT_TAG_REGEX, " ");
        // 3. 替换掉普通的标签
        content = content.replaceAll(RegexPatterns.COMMON_TAG_REGEX, " ");
        // 4. 使用正则把多个空格合并成一个空格
        content = content.replaceAll(RegexPatterns.MORE_SPACES_REGEX, " ");
        return content;
    }

    private String readFile(File file) {
        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
            StringBuilder stringBuilder = new StringBuilder();
            while(true){
                int ret = bufferedReader.read();
                if(ret == -1){
                    break;
                }
                char c = (char) ret;
                if(c == '\n' || c == '\r'){
                    c = ' ';
                }
                stringBuilder.append(c);
            }
            return stringBuilder.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    private String parseURL(File file) {
        String suffixPath = file.getAbsolutePath().substring(SystemConstants.FILE_LOADING_PATH.length());
        String result = SystemConstants.FILE_ONLINE_PATH + suffixPath;
        return result;
    }

    private String parseTitle(File file) {
        String name = file.getName();
        String result = name.substring(0, name.length() - SystemConstants.HTML_SUFFIX_SIZE);
        return result;
    }

    /**
     * 递归遍历指定路径中的所有文件
     *
     * @param fileLoadingPath 表示从哪个目录开始进行递归遍历
     * @param fileList        表示递归得到的结果
     */
    private void enumFile(String fileLoadingPath, ArrayList<File> fileList) {
        File rootPath = new File(fileLoadingPath);
        // listFiles 能够获取到当前目录包含的文件/目录（只能看到一级目录，需要进行递归）
        File[] files = rootPath.listFiles();
        // 遍历得到的文件，进行递归
        for (File file : files) {
            // 如果是目录，递归
            if (file.isDirectory()) {
                enumFile(file.getAbsolutePath(), fileList);
            } else {
                // 排除除了 .html 之外的文件
                if (file.getAbsolutePath().endsWith(".html")) {
                    fileList.add(file);
                }
            }
        }
    }

//    public static void main(String[] args) {
//        // 通过 main 方法来实现整个制作索引的过程
//        Parser parser = new Parser();
//        try {
//            parser.runByThread();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//    }

}
